#include "timer.h"
#include "crossplatform.h"
#include <map>

class CommonTimer;
typedef std::multimap<unsigned long long, CommonTimer*> TimerMap;

static CriticalSection PendingLock;
static TimerMap PendingTimers;
static NotificationEvent NewTimerEvent;

#ifdef WIN32
#pragma comment(lib, "winmm.lib")
#endif

typedef void (*PoolExecCb)(void *arg);
struct ThreadPoolEntry
{
	ThreadPoolEntry *Next;
	PoolExecCb Cb;
	void *Arg;
};

static ProducerConsumerFifo<ThreadPoolEntry> PoolFifo;
static volatile long PoolThreadsIdle = 0;

void* PoolThread(void *arg)
{
	ThreadPoolEntry *e;
	while((e = PoolFifo.DequeueWait()) != NULL) {
		if(!InterlockedDecrement(&PoolThreadsIdle)) {
			InterlockedIncrement(&PoolThreadsIdle);
			SpawnThread(PoolThread, arg, NULL);
		}
		e->Cb(e->Arg);
		delete e;
		InterlockedIncrement(&PoolThreadsIdle);
	}
	return 0;
}

void PoolExec(PoolExecCb fn, void *arg)
{
	if(PoolThreadsIdle == 0) {
		InterlockedIncrement(&PoolThreadsIdle);
		SpawnThread(PoolThread, 0, NULL);
	}

	ThreadPoolEntry *e = new ThreadPoolEntry();
	e->Arg = arg;
	e->Cb = fn;
	PoolFifo.Queue(e);
}

unsigned long long GetTimestamp()
{
#ifdef WIN32
	return timeGetTime();
	FILETIME ft;
	GetSystemTimeAsFileTime(&ft);

	unsigned long long v = ft.dwHighDateTime;
	v <<= 32;
	v |= ft.dwLowDateTime;
	return v / 10000;
#else
	timeval tv;
	gettimeofday(&tv, NULL);
	unsigned long long ms = tv.tv_sec;
	ms *= 1000;
	return ms + tv.tv_usec / 1000;
#endif
}

class CommonTimer : public GenericTimer
{
public:
	CommonTimer(GenericTimerCallback *cb)
	{
		Callbacks = cb;
		CallbackWait.Signal();
		IsQueued = false;
	}

	~CommonTimer()
	{
		CancelTimer();
	}

	virtual void ScheduleTimer(unsigned int ms)
	{
		AutoCriticalSection cs(PendingLock);
		{
			CancelTimer();

			unsigned long long time = GetTimestamp() + ms;
			SelfIter = PendingTimers.insert(TimerMap::value_type(time, this));
			IsQueued = true;
			if(SelfIter == PendingTimers.begin()) {
				ScheduledEarlierTimer(time);
			}
		}
	}

	virtual void CancelTimer()
	{
		AutoCriticalSection cs(PendingLock);
		{
			if(IsQueued) {
				PendingTimers.erase(SelfIter);
				IsQueued = false;
				CallbackWait.Wait();
			}
		}
	}

private:
	static void CallExpired(void *arg)
	{
		((CommonTimer*)arg)->Callbacks->TimerExpired();
		((CommonTimer*)arg)->CallbackWait.Signal();
	}

	void Expired()
	{
		IsQueued = false;
		CallbackWait.Clear();
		PoolExec(CallExpired, this);
	}

	static void* TimerExecThread(void *arg)
	{
		unsigned int delay = 10;
		for(;;) {
			NewTimerEvent.Wait(delay);
			NewTimerEvent.Clear();
			AutoCriticalSection cs(PendingLock);
			unsigned long long at = GetTimestamp();
			{
				while(!PendingTimers.empty()) {
					std::pair<unsigned long long, CommonTimer*> pair = *PendingTimers.begin();
					if(pair.first > at + 10) {
						delay = (unsigned int)(pair.first - at);
						break;
					}
					PendingTimers.erase(PendingTimers.begin());
					pair.second->Expired();
				}
			}
		}
	}

	static void ScheduledEarlierTimer(unsigned long long time)
	{
		static bool started = false;
		if(!started) {
			started = true;
			SpawnThread(TimerExecThread, 0, 0);
		}
		NewTimerEvent.Signal();
	}

	GenericTimerCallback *Callbacks;
	TimerMap::iterator SelfIter;
	NotificationEvent CallbackWait;
	bool IsQueued;
};

GenericTimer* GenericTimer::CreateOsTimer(GenericTimerCallback *cb)
{
	return new CommonTimer(cb);
}

